5.07. PHP
Основы языка
PHP — редкий пример языка, возникшего не из академических или системных потребностей, а из конкретной инженерной задачи: встраивание логики в HTML-страницы. Первая версия — Personal Home Page Tools (PHP/FI), написанная Расмусом Лердорфом в 1994 году — представляла собой набор CGI-скриптов на C, предназначенных для отслеживания посещений его резюме. Уже на этом этапе проявилась ключевая черта, определившая эволюцию PHP: ориентация на веб как на среду исполнения. В отличие от Perl, который тогда широко использовался для CGI, или Java Servlet, требовавших сложной инфраструктуры, PHP изначально задумывался как язык минимального сопротивления: встроенный в HTTP-сервер, без необходимости явного управления циклом запросов, с доступом ко всем параметрам запроса «из коробки».
С тех пор PHP прошёл путь от утилитарного препроцессора к полноценному языку общего назначения. Однако его архитектурная ДНК остаётся неизменной: язык не пытается абстрагироваться от HTTP, а, напротив, интегрирует его в своё ядро. Это определяет как сильные стороны (быстрое создание веб-интерфейсов, естественная работа с формами и сессиями), так и ограничения (избыточность веб-специфичных конструкций в CLI-контексте). Сегодня PHP — это не «язык для сайтов», а язык с сильной веб-специализацией, способный, при необходимости, выйти за её пределы.
Современное определение — PHP: Hypertext Preprocessor — уже является рекурсивным акронимом, что символично: язык описывает сам себя, постоянно переосмысливая и переписывая собственные основы (от процедурного стиля до строгой типизации и метапрограммирования), сохраняя при этом обратную совместимость как один из ключевых принципов.
Концептуальные основы: как устроен PHP «изнутри»
1. Жизненный цикл скрипта: от запроса к ответу
В отличие от долгоживущих серверных приложений (Node.js, Java), PHP по умолчанию работает в модели однократного исполнения. Каждый HTTP-запрос порождает отдельный процесс (или поток в SAPI-режиме), в котором:
- Инициализируется интерпретатор (загружаются расширения, OPcache, инициализируются суперглобальные массивы);
- Выполняется код PHP-файла (или цепочки файлов через
include); - Генерируется HTTP-ответ (заголовки, тело документа);
- Процесс завершается, освобождая всю память.
Эта модель упрощает управление состоянием (не нужно заботиться о памяти между запросами), но накладывает ограничения на долгие операции и кэширование в памяти. С появлением SAPI-интерфейсов вроде php-fpm и RoadRunner/Swoole, PHP приобрёл возможность работать и в долгоживущих режимах (worker-based), что расширило его применение в микросервисах и высоконагруженных API, однако классическая модель остаётся доминирующей и определяет философию ядра.
2. Интерпретация и оптимизация: OPcache как компромисс
Технически PHP не является чистым интерпретатором. Современный движок Zend Engine (начиная с PHP 7) преобразует исходный код в промежуточное представление — OPcode (операционный код), аналогичное байт-коду в Java или Python. Этот OPcode затем выполняется виртуальной машиной Zend VM.
Ключевая оптимизация — OPcache (включён по умолчанию с PHP 5.5) — кэширует скомпилированный OPcode в разделяемой памяти, устраняя повторную токенизацию и компиляцию при каждом запросе. Это превращает PHP в частично компилируемый язык: время запуска становится сопоставимым с JIT-компиляцией (особенно после PHP 8.0, где Zend Engine получил улучшенный оптимизатор), а сам код остаётся гибким и интерпретируемым на этапе разработки.
Важно подчеркнуть: OPcache не меняет семантику языка. Даже при кэшировании OPcode переменные остаются динамическими, типы — нефиксированными (если не включена строгая типизация), а поведение include — динамическим. OPcache — это ускорение инициализации, а не трансформация в статически типизированный компилируемый язык.
3. Динамическая типизация: гибкость и её стоимость
PHP — язык с динамической, слабой типизацией по умолчанию. Это означает, что:
- Тип переменной не объявляется и определяется во время выполнения на основе значения;
- Автоматические преобразования между типами происходят неявно в большинстве операций;
- Одна и та же переменная может менять тип в ходе выполнения скрипта.
Эта модель обеспечивает высокую скорость прототипирования: разработчик может сосредоточиться на логике, не тратя время на декларацию типов и управление памятью. Однако она влечёт за собой две системные проблемы:
Во-первых, неопределённость поведения. Например:
echo "10 apples" + 2; // → 12 (строка преобразуется к числу по правилам "leading numeric string")
echo "apples 10" + 2; // → 2 (строка без ведущего числа → 0)
Предсказуемость требует глубокого знания правил преобразования (см. спецификацию convert_to_number() в Zend Engine).
Во-вторых, сложность статического анализа. Без явных типов IDE и анализаторы кода не могут гарантировать корректность вызовов. Для решения этой проблемы в PHP 7.0 была введена строгая типизация через директиву declare(strict_types=1), которая:
- Применяется на уровне файла;
- Требует точного совпадения типов аргументов и возвращаемых значений (исключения —
null, если указан?T); - Не отменяет динамическую природу языка, а лишь ограничивает её в определённых контекстах.
Строгая типизация — не замена динамической, а инструмент локального ужесточения в критически важных участках кода (например, в слое домена или API-контрактах). Это компромисс, характерный для PHP: сохранение гибкости в целом при предоставлении механизмов контроля там, где он необходим.
4. HTTP как часть языка
В PHP нет необходимости импортировать HTTP-библиотеки или инициализировать серверный объект — HTTP — это окружение по умолчанию. С момента старта скрипта доступны:
- Суперглобальные массивы — прямой доступ к параметрам запроса и метаданным;
- Встроенные функции для работы с заголовками (
header()), статусами (http_response_code()), cookie (setcookie()), сессиями (session_start()); - Автоматическая обработка
multipart/form-dataи помещение загруженных файлов в$_FILES.
Это достигается за счёт тесной интеграции с SAPI (Server API) — интерфейсом, через который PHP взаимодействует с веб-сервером (Apache, Nginx через php-fpm, CLI и др.). Каждый SAPI предоставляет свои реализации функций ввода-вывода, что делает поведение PHP контекст-зависимым. Например, header() в CLI-режиме вызывает ошибку, поскольку нет HTTP-ответа.
Такая интеграция — двусторонний меч: с одной стороны, она устраняет «бумажную работу» при создании веб-приложений; с другой — затрудняет тестирование (требуется мокать глобальные состояния) и усложняет архитектурную чистоту (логика зависит от глобального окружения). Современные фреймворки (Symfony, Laravel) решают эту проблему, инкапсулируя HTTP-взаимодействие в объекты запроса и ответа (Request, Response), но на уровне ядра PHP остаётся «серверным скриптовым языком».
Суперглобальные массивы: окна в окружение
Суперглобальные массивы — не просто удобные переменные, а артефакты архитектуры однозапросного исполнения. Поскольку между запросами нет постоянного состояния, каждый новый запрос должен получить полное описание контекста в виде структурированных данных. PHP предоставляет эти данные в виде предопределённых массивов, доступных в любой точке скрипта без объявления.
| Массив | Источник данных | Особенности |
|---|---|---|
$_GET | Query string (URL после ?) | Все значения — строки. Подвержен инъекциям (например, ?id[]=1&id[]=2 → массив). |
$_POST | Тело запроса с Content-Type: application/x-www-form-urlencoded или multipart/form-data | Автоматически парсится PHP; для JSON требуется ручной json_decode(file_get_contents('php://input')). |
$_COOKIE | Заголовок Cookie из запроса | PHP не проверяет корректность имён/значений — ответственность за валидацию лежит на разработчике. |
$_SESSION | Хранилище (файлы, Redis, БД), идентифицируемое по PHPSESSID из cookie или URL | Требует вызова session_start() до чтения/записи. Не является «глобальным» в классическом смысле — зависит от идентификатора сессии. |
$_SERVER | Переменные окружения + заголовки HTTP (префикс HTTP_) + метаданные запроса | Единственный массив, содержащий как клиентские (HTTP_USER_AGENT), так и серверные (DOCUMENT_ROOT, SCRIPT_FILENAME) данные. |
$_FILES | Только при Content-Type: multipart/form-data | Содержит структуру ['name', 'type', 'tmp_name', 'error', 'size'] для каждого поля <input type="file">. |
$_REQUEST | Объединение $_GET, $_POST, $_COOKIE (в порядке, задаваемом variables_order) | Из-за неопределённости источника не рекомендуется к использованию в production-коде. |
$_ENV | Переменные окружения процесса | Доступны только при включённой директиве variables_order, включающей E. |
Важнейшее замечание: суперглобальные — это не «данные пользователя», а «сырые входные данные». Они не проходят автоматической санитизации. Любой доступ к $_GET['id'] без валидации и экранирования — это потенциальная уязвимость (XSS, SQLi, LFI). Современная практика предписывает обёртывать их в контроллеры или DTO с явной валидацией, что, однако, остаётся задачей фреймворков, а не языка.
Синтаксическая эволюция: от Perl к современности
Синтаксис PHP унаследовал черты трёх языков:
- C — управляющие конструкции (
if,for,while), операторы (++,+=), фигурные скобки; - Perl — обработка строк (
$varв двойных кавычках), регулярные выражения (PCRE), суперглобальные; - Java — синтаксис классов, интерфейсов, исключений (появился в PHP 5).
С течением времени PHP постепенно отходил от «скриптового» стиля к более строгому и выразительному:
- PHP 5.3: анонимные функции, замыкания (
use), пространства имён; - PHP 5.6: оператор
...(splat), константыconstв классах; - PHP 7.0: строгая типизация, скалярные типы, возврат
void, null coalescing (??); - PHP 7.4: стрелочные функции, typed properties;
- PHP 8.0: union types, match-expression, JIT-компиляция (ограниченно), nullsafe operator (
?->); - PHP 8.1: enums, readonly properties.
Ключевая тенденция — постепенное введение статических гарантий без отказа от динамики. Например, union types (string|int) позволяют описать вариативность, характерную для веб-данных, не теряя при этом проверки на этапе выполнения. Это отражает философию PHP: не навязывать парадигму, а предоставлять инструменты для выбора уровня контроля в зависимости от контекста задачи.
Модель исполнения: SAPI как интерфейс к миру
SAPI (Server Application Programming Interface) — это интерфейс между ядром PHP (Zend Engine) и внешним окружением. От выбора SAPI зависит не только способ запуска скрипта, но и его поведение при работе с потоками ввода-вывода, переменными окружения, жизненным циклом и даже доступностью некоторых функций.
PHP поставляется с несколькими встроенными SAPI, наиболее значимые из которых:
| SAPI | Описание | Особенности поведения |
|---|---|---|
CLI (php script.php) | Command-Line Interface | — argc, argv доступны глобально; — STDIN, STDOUT, STDERR — ресурсы; — header(), setcookie() вызывают ошибку (нет HTTP-контекста); — $_SERVER['argv'], $_SERVER['argc'], $_SERVER['PHP_SELF'] отличаются от веб-режима; — php.ini: cli/php.ini может отличаться от fpm/php.ini. |
| php-fpm (FastCGI Process Manager) | Стандарт для Nginx/Apache (через proxy_pass или mod_proxy_fcgi) | — Работает в режиме пула воркеров (долгоживущие процессы); — Поддерживает graceful restart, управление памятью, лимиты запросов; — Позволяет использовать OPcache эффективно (кэш сохраняется между запросами); — Каждый воркер обрабатывает запросы последовательно (не конкурентно внутри процесса). |
Apache module (mod_php) | Встраивается непосредственно в Apache | — Устаревший, но исторически значимый режим; — Каждый дочерний процесс Apache загружает PHP целиком; — Проблемы с памятью при большом числе воркеров (PHP не выгружается между запросами); — Не поддерживает разные php.ini для разных виртуальных хостов. |
Embedded (embed) | Встраивание PHP в стороннее приложение (редко) | — Позволяет использовать PHP как скриптовый движок внутри C/C++-приложения; — Используется, например, в некоторых CMS-системах с кастомным хостом. |
Ключевое следствие: код, написанный без учёта SAPI, может вести себя по-разному. Например:
// В CLI:
echo $_SERVER['REQUEST_METHOD']; // Notice: Undefined index
// В php-fpm:
echo $_SERVER['REQUEST_METHOD']; // → GET
// В CLI:
echo $_SERVER['SCRIPT_NAME']; // → /path/to/script.php
// В веб-режиме:
echo $_SERVER['SCRIPT_NAME']; // → /index.php
Современная практика — явное определение SAPI через PHP_SAPI:
if (PHP_SAPI === 'cli') {
// CLI-логика
} elseif (in_array(PHP_SAPI, ['fpm-fcgi', 'apache2handler'])) {
// Веб-логика
}
Это не «костыль», а осознанная адаптация к контексту, заложенная в философию ядра: PHP не навязывает окружение — он реагирует на него.
Жизненный цикл скрипта: инициализация, выполнение, завершение
Каждый запуск PHP-скрипта проходит чётко определённые фазы, управляемые Zend Engine:
-
MINIT (Module Initialization)
Выполняется один раз при запуске SAPI (например, при старте php-fpm-воркера). Инициализируются расширения, глобальные структуры, OPcache.
Не зависит от запроса. -
RINIT (Request Initialization)
Выполняется перед каждым запросом.
— Парситсяphp.ini, загружаются настройки (если не закэшированы);
— Инициализируются суперглобальные массивы ($_GET,$_POST,$_SERVERи др.);
— Выполняетсяauto_prepend_file(если указан);
— Выделяется пул памяти для запроса (arena allocator). -
Execution
Интерпретируется OPcode (либо компилируется JIT в PHP 8+).
— Выполняютсяinclude,require, определения классов/функций;
— Создаются и уничтожаются локальные переменные в стеке вызовов;
— Могут срабатывать автозагрузчики (spl_autoload_register). -
RSHUTDOWN (Request Shutdown)
После генерации ответа (или аварийного завершения):
— Выполняетсяauto_append_file;
— Завершаются сессии (session_write_closeприsession.auto_start);
— Освобождается память запроса;
— Сбрасываются статические переменные внутри функций и методов. -
MSHUTDOWN (Module Shutdown)
При завершении процесса (например,kill -TERMдля fpm-воркера).
— Выгружаются расширения;
— Освобождается разделяемая память (включая OPcache).
Этот цикл объясняет, почему статические переменные в функциях не сохраняются между запросами в традиционной модели, но могут сохраняться в долгоживущих SAPI (например, при использовании Swoole). Это также определяет границы действия register_shutdown_function() — он вызывается в RSHUTDOWN, а не после него.
Обработка ошибок и исключений: иерархия реакций
В PHP реализована двуслойная система обработки аномалий:
1. Ошибки (errors) — на уровне исполнения
Генерируются ядром или расширениями при нарушении предусловий выполнения (например, деление на ноль, обращение к несуществующему индексу массива, вызов несуществующей функции). До PHP 7 ошибки не были исключениями и не перехватывались try/catch.
Начиная с PHP 7, большинство фатальных и recoverable ошибок преобразуются в Error (и его подклассы: TypeError, ParseError, ArgumentCountError), которые наследуют от Throwable, как и Exception. Это означает:
try {
$arr = [];
echo $arr['missing']; // PHP Notice → в PHP 7+ не бросает исключение
undefined_function(); // → Error (Fatal Error)
} catch (Error $e) {
// Перехват фатальной ошибки как исключения
}
Однако notices и warnings по-прежнему не являются исключениями. Их можно перехватить только через set_error_handler():
set_error_handler(function($errno, $errstr, $errfile, $errline) {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});
Такой подход — компромисс: сохранение совместимости (старый код с E_NOTICE продолжает работать), но предоставление пути к строгой обработке.
2. Исключения (exceptions) — на уровне логики
Явно выбрасываются через throw, перехватываются через try/catch. В PHP 8.0 появилась поддержка catch без переменной:
try {
$value = $nullable ?? throw new \InvalidArgumentException();
} catch (\InvalidArgumentException) {
// Переменная не нужна — только логирование/переход
}
А в PHP 8.4 (на момент 2025 г. — в стадии финального релиза) появится Error как строгий тип по умолчанию для необработанных исключений, что усилит гарантии.
Важно: Error и Exception — две ветви одной иерархии (Throwable), но они имеют разное происхождение и семантику:
Exception— логическое нарушение (невалидные данные, бизнес-ошибка);Error— системное нарушение (ошибка программиста, нарушение контракта языка).
Разделение позволяет строить многоуровневую стратегию обработки: например, TypeError можно логировать как баг, а ValidationException — как часть нормального потока.
Безопасность «из коробки»: что делает ядро, а что — разработчик
PHP не обеспечивает безопасность автоматически, но предоставляет механизмы, позволяющие её построить.
1. Регистр переменных (magic_quotes, register_globals)
Устаревшие механизмы, полностью удалённые в PHP 5.4+, служат напоминанием: попытки «автоматической» защиты (например, экранирование кавычек в $_GET) лишь создавали ложное чувство безопасности и усложняли отладку. Современный PHP не модифицирует входные данные.
2. Сессии
— Генерация session.id по умолчанию использует CSPRNG (с PHP 7.1);
— Поддержка строгого режима (session.use_strict_mode = 1) — запрет использования неизвестных ID;
— Возможность привязки сессии к IP/User-Agent (хотя это спорная практика);
— Встроенные обработчики хранения: files, redis, memcached, pdo.
Но: PHP не шифрует данные сессии на стороне сервера и не защищает от фиксации ID (session fixation) без дополнительных мер.
3. Фильтрация и валидация
Расширение filter (filter_var(), filter_input()) предоставляет встроенные механизмы:
$email = filter_input(INPUT_GET, 'email', FILTER_VALIDATE_EMAIL);
$int = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT, [
'options' => ['min_range' => 1, 'max_range' => 1000]
]);
Однако FILTER_SANITIZE_STRING удалён в PHP 8.1 как потенциально опасный (слишком оптимистичная санитизация). Корректный подход — валидация + экранирование на выходе (например, htmlspecialchars() для HTML).
4. Открытый доступ к файловой системе
Функции include, require, file_get_contents, fopen работают с абсолютными и относительными путями, включая http://, php://, data://. Это мощно, но опасно:
include $_GET['page'] . '.php'; // LFI-уязвимость
Ограничения:
open_basedir— ограничение доступа к файлам за пределами указанных директорий;allow_url_fopen,allow_url_include— управление включением удалённых ресурсов.
Но эти директивы не включены по умолчанию. Безопасность — в архитектуре приложения.
Расширяемость: как PHP становится «своим»
PHP поддерживает расширяемость на трёх уровнях:
1. Функции и классы пользовательского уровня
— function, class, trait, interface — основа повторного использования;
— spl_autoload_register() — динамическая загрузка классов;
— Пространства имён (namespace) — изоляция имён.
2. Расширения на C (zend_extension, extension)
Компилируемые модули, работающие на уровне Zend Engine:
opcache.so— кэширование OPcode;xdebug.so— отладка и профилирование;redis.so,mongodb.so— драйверы БД.
Разработка расширений требует знания C и API Zend Engine, но даёт максимальную производительность.
3. Stream wrappers и stream filters
Механизм, позволяющий определить собственное поведение для URL-подобных строк:
stream_wrapper_register('myproto', MyProtoStream::class);
file_get_contents('myproto://resource'); // вызовет MyProtoStream::stream_open()
Аналогично, stream_filter_register() позволяет встраивать трансформации (например, zlib.deflate, convert.iconv.*).
Это делает PHP языком-платформой: он не просто исполняет код — он предоставляет интерфейсы для построения собственных сред внутри себя.
Архитектурные паттерны: от «спагетти» к слоям
Хотя язык не навязывает архитектуру, в экосистеме PHP сложились устойчивые модели:
| Паттерн | Характеристики | Примеры |
|---|---|---|
| Процедурный (mixed) | HTML + PHP в одном файле, глобальные переменные, include как композиция | Ранний WordPress, «классические» PHP-сайты 2000-х |
| Front Controller | Один точка входа (index.php), маршрутизация через $_SERVER['REQUEST_URI'] | CodeIgniter (частично), ручная реализация |
| MVC (Model-View-Controller) | Чёткое разделение: логика → модели, представление → шаблоны, связка → контроллеры | Laravel, Symfony, CakePHP |
| Middleware (PSR-15) | Последовательная обработка запроса через цепочку делегатов | Slim, Zend Stratigility, Laminas Mezzio |
| Domain-Driven Design (DDD) | Слой домена → прикладной слой → инфраструктурный слой | Sylius, Laravel + packages (Laravel Actions, Spatie DataTransferObject) |
Ключевая эволюция — от глобального состояния к инверсии управления:
- Ранний PHP:
$db = new PDO(...);в каждом файле; - Современный PHP:
ContainerInterface,Dependency Injection,Service Locator(но DI предпочтительнее).
Стандарты PSR (PHP Standard Recommendations) сыграли решающую роль:
- PSR-4 — автозагрузка;
- PSR-7 — HTTP-сообщения (
Request,Response); - PSR-15 — middleware;
- PSR-18 — HTTP-клиент.
Они не являются частью языка, но формируют среду, в которой PHP используется как компонент крупной системы, а не как «скрипт в корне сайта».
Совместимость и управление версиями: от монолита к композиту
Исторически PHP развивался в режиме монолитной совместимости: каждая новая версия стремилась к обратной совместимости на уровне синтаксиса и поведения функций. Однако с ростом сложности языка и экосистемы (особенно после перехода на Composer и компонентную архитектуру) эта парадигма уступила место управляемой несовместимости.
1. Семантическое версионирование в экосистеме
Сам PHP не следует SemVer — его версии (8.1, 8.2, 8.3, 8.4) отражают мажорные релизы с новыми возможностями и потенциальными BC-разрывами. Однако пакеты, устанавливаемые через Composer, строго придерживаются SemVer 2.0:
1.2.3→MAJOR.MINOR.PATCH;^1.2.0— совместимость по API: допускает1.3.0, но не2.0.0;~1.2.0— совместимость по патчам и минорным версиям в пределах1.2.x.
Критически важна директива platform в composer.json:
{
"config": {
"platform": {
"php": "8.1.0"
}
}
}
Она фиксирует целевую версию PHP при разрешении зависимостей, предотвращая установку пакетов, требующих ext-redis:^6.0 на сервере с ext-redis:5.3.7. Это позволяет разрабатывать на PHP 8.4, но деплоить на PHP 8.1 — без риска подтянуть неподдерживаемые зависимости.
2. Управление расширениями
PHP поставляется в виде ядра и опциональных расширений (ext-*). Состав расширений — не часть языка, а свойство окружения. Это создаёт проблему переносимости: код, использующий imagick, не запустится без ext-imagick.
Composer позволяет декларировать зависимости от расширений:
{
"require": {
"ext-pdo": "*",
"ext-json": "*",
"ext-redis": "^5.3.0"
}
}
При composer install будет проверена доступность расширений — и ошибка выдана до запуска кода. Это переносит проверку из runtime в этап сборки, повышая надёжность.
Важно: PHP не имеет «стандартной библиотеки» в стиле Python или Java. Даже
json_encode()— часть расширенияext-json, которое может быть отключено. В production-сборках рекомендуется явно фиксировать список необходимых расширений.
Взаимодействие с внешними системами: границы исполнения
PHP — не изолированный исполнитель; он встраивается в экосистему ОС и сетевых сервисов. Ядро предоставляет три основных механизма интеграции:
1. Системные вызовы и процессы
Функции exec(), shell_exec(), system(), passthru(), proc_open() позволяют запускать внешние программы. Ключевые отличия:
| Функция | Возврат | Потоки | Безопасность |
|---|---|---|---|
exec($cmd, &$output, &$code) | Последняя строка stdout | stdout → массив $output | Требует экранирования (escapeshellarg()) |
shell_exec($cmd) | Полный stdout как строка | stderr смешивается с stdout | Аналогично |
proc_open($cmd, $descriptors, &$pipes) | Ресурс процесса | Полный контроль: stdin/stdout/stderr как потоки | Возможен non-blocking I/O |
Пример безопасного вызова:
$filename = escapeshellarg($_GET['file']);
$output = shell_exec("cat $filename 2>&1");
Без escapeshellarg() — RCE-уязвимость.
2. Foreign Function Interface (FFI)
Начиная с PHP 7.4, в ядро интегрирован FFI — механизм прямого вызова функций из разделяемых библиотек (*.so, *.dll) без написания C-расширения:
$libc = FFI::cdef("
int printf(const char *format, ...);
", "libc.so.6");
$libc->printf("Hello %s!\n", "FFI"); // → Hello FFI!
FFI — не «мост к C», а интерфейс к двоичному ABI. Он требует знания структур данных и соглашений вызова (cdecl, stdcall), но позволяет:
- Интегрировать legacy-библиотеки (например,
libzдля сжатия); - Писать высокопроизводительные примитивы (например, хэш-функции);
- Встраивать движки (Lua, V8) без посредника.
Ограничение: FFI отключён по умолчанию в php.ini (ffi.enable = preload или true), так как нарушает sandboxing.
3. Потоки и обёртки (streams)
PHP-потоки (php://, http://, data://, compress.zlib://) — единый интерфейс для работы с любыми источниками данных, абстрагирующий протокол и транспорт:
// Чтение из stdin
$input = file_get_contents('php://stdin');
// Отправка в сжатый буфер
file_put_contents('compress.zlib://output.gz', $data);
// Создание временного файла в памяти
$temp = fopen('php://temp/maxmemory:1048576', 'w+');
Каждая обёртка реализует интерфейс stream_wrapper:
stream_open(),stream_read(),stream_write(),stream_close()— базовые операции;stream_stat(),url_stat()— метаданные;stream_cast()— получение нативного дескриптора.
Это позволяет писать, например, s3://bucket/key как обычный файл — при наличии aws/aws-sdk-php с поддержкой stream wrapper’а.
Модель распределённого состояния: сессии, кэш, очередь
В веб-среде состояние между запросами не сохраняется. PHP предоставляет механизмы для явного управления распределённым состоянием — но не реализует их «из коробки», а лишь задаёт интерфейсы.
1. Сессии: контракт, а не реализация
Функции session_start(), $_SESSION, session_regenerate_id() работают через обработчик сессий (session.save_handler). Ядро включает:
files— хранение в/tmp/sess_*(по умолчанию);user— кастомный обработчик черезsession_set_save_handler().
Современные приложения почти всегда заменяют files на:
redis(черезext-redis);memcached;pdo(собственный DSN).
Ключевые параметры php.ini:
session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379?database=2"
session.gc_maxlifetime = 1440 ; время жизни в секундах
session.cookie_secure = 1 ; только по HTTPS
session.cookie_httponly = 1 ; недоступна из JS
Сессии — не место для хранения данных. Они предназначены для идентификации и аутентификации. Данные должны храниться в БД, а в сессии — только ID.
2. Кэширование: от APCu к PSR-6
- APCu (
apc.enable_cli=1при необходимости) — локальный in-memory кэш для одного процесса (не shared между воркерами!); - Redis/Memcached — распределённый кэш, совместимый с PSR-6 (
CacheItemPoolInterface); - OPcache — кэш кода, а не данных.
Важно различать:
opcache_reset()— сброс OPcache (требуетopcache.restrict_api);apcu_clear_cache()— сброс APCu;$redis->flushdb()— сброс Redis.
3. Асинхронность и очереди
PHP — однопоточный, но поддерживает асинхронные паттерны:
-
CLI-очереди (Laravel Queues, Symfony Messenger):
php artisan queue:work --daemonЗадачи сериализуются в Redis/RabbitMQ/DB, обрабатываются воркерами.
-
Non-blocking I/O (через
stream_select(),ReactPHP,Amp):$socket = stream_socket_server("tcp://127.0.0.1:8080");
stream_set_blocking($socket, false);
$read = [$socket];
while (true) {
$changed = $read;
stream_select($changed, $write, $except, null);
// обработка событий
} -
Корутины (Swoole, RoadRunner):
Co\run(function () {
$redis = new Co\Redis;
$redis->connect('127.0.0.1', 6379);
$val = $redis->get('key');
});Переключение контекста происходит на
yield-точках (I/O), а не на потоках.
Эволюция стандартов: от PEAR к PSR и Composer
PHP прошёл путь от централизованного репозитория (PEAR) к децентрализованной экосистеме (Packagist + Composer), управляемой сообществом через PHP-FIG (Framework Interop Group).
| Этап | Инструмент | Проблема | Решение |
|---|---|---|---|
| До 2009 | Ручное копирование файлов, include_path | Конфликты имён, отсутствие зависимостей | — |
| 2009–2012 | PEAR, PECL | Централизация, монолитные пакеты, отсутствие versioning | — |
| 2012–2014 | Composer, Packagist | Фрагментация, несовместимость | PSR-0 → PSR-4 (автозагрузка) |
| 2015–настоящее | PSR-7 (HTTP), PSR-15 (Middleware), PSR-18 (HTTP Client) | Нестандартизированные контракты | Интерфейсы вместо реализаций |
Ключевой результат — интероперабельность:
- Вы можете использовать
GuzzleHttp\Client(PSR-18) с Symfony HttpClient через адаптер; - Middleware от Slim работает в Laravel через
laravel/psr15-bridge; - Логгер Monolog (
Psr\Log\LoggerInterface) интегрируется в любой фреймворк.
Это превратило PHP из «языка с фреймворками» в платформу с компонентами.